import logging, math, random, sys, urllib
from datetime import datetime
from django.conf import settings
from django.contrib.auth.hashers import make_password, check_password
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from tools.id_tools import wikipedia_id_from_movie, movie_from_inputs, movie_from_imdb_input, netflix_movie_from_title, movie_from_netflix_input, rottentomatoes_movie_from_title, movie_from_rottentomatoes_input
from tools.misc_tools import create_properties, create_movie_property, imdb_link_for_movie, rottentomatoes_link_for_movie, netflix_link_for_movie, wikipedia_link_for_movie, logout_command, login_command, get_logged_in_profile, person_is_relevant, genre_is_relevant, generate_header_dict, set_msg, update_rankings
from tools.search_tools import movies_from_term
from movies.models import Profiles, People, Genres, Movies, MovieProperties, ProfileMovies
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

site_logger = logging.getLogger('log.site')
profile_logger = logging.getLogger('log.profile')
movie_logger = logging.getLogger('log.movie')
property_logger = logging.getLogger('log.property')
associate_logger = logging.getLogger('log.associate')

# Landing page for everyone that requires a group password or profile
def access(request):
	try:
		if request.method == 'POST':
			'''*****************************************************************************
			Create profile and redirect to home on success or back to register on failure
			PATH: movies.views.access - *See urls.py
			METHOD: post - *Required to get to function
			PARAMS: none - *Required to get to function
			MISC: none - *Required to get to function
			*****************************************************************************'''
			if request.POST.get('access_password') == settings.ACCESS_PASSWORD:
				site_logger.info('Access Success')
				request.session['auth_access'] = True
				# Five minutes to create a profile or login
				request.session.set_expiry(300)
				set_msg(request, 'Access Granted!', 'Welcome, you have successfully accessed the site.', 3)
				return redirect('movies.views.home')
			else:
				set_msg(request, 'Access Denied!', 'Password not correct', 4)
				return redirect('movies.views.access')
		else:
			'''*****************************************************************************
			Display access page
			PATH: movies.views.access; METHOD: not post; PARAMS: none; MISC: none;
			*****************************************************************************'''
			# Mandatory check in every function that checks if user is logged in (or has access for a few pages like this one)
			logged_in_profile_id = request.session.get('auth_profile_id')
			logged_in_profile_username = request.session.get('auth_profile_username')
			logged_in_profile_admin = request.session.get('admin')
			if not logged_in_profile_id:
				access = request.session.get('auth_access')
				if not access:
					set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
					return render_to_response('access.html', {'header' : generate_header_dict(request, 'Access')}, RequestContext(request))
			return redirect('movies.views.home')
			
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Landing page after login or profile creation
def home(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			access = request.session.get('auth_access')
			if not access:
				set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
				return redirect('movies.views.access')
		if request.GET.get('error') and request.method == 'POST':
			'''*****************************************************************************
			Error page will submit user email here (could be delegated to an error function)
			PATH: movies.views.home; METHOD: post; PARAMS: get - error; MISC: none;
			*****************************************************************************'''
			profile, email_from, email_subject = None, 'Anonymous', 'Error'
			try:
				profile = Profiles.objects.get(id=logged_in_profile_id)
			except Exception:
				pass
			if profile:
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id) + ' Error'
			email_message = request.POST.get('message') if request.POST.get('message') else None
			set_msg(request, 'Thank you for your feedback!', 'We have recieved your input and will react to it appropriately.', 3)
			if email_message:
				# send email
				pass
			else:
				pass
			return redirect('movies.views.home')
		'''*****************************************************************************
		Display home page
		PATH: movies.views.home; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		return render_to_response('home.html', {'header' : generate_header_dict(request, 'Welcome to Pref')}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# User registration/Profile creation
def register(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			access = request.session.get('auth_access')
			if not access:
				set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
				return redirect('movies.views.access')
		if request.method == 'POST':
			'''*****************************************************************************
			Create profile and redirect to home on success or back to register on failure
			PATH: movies.views.register; METHOD: post; PARAMS: none; MISC: none;
			*****************************************************************************'''
			# Defaults
			profile = Profiles()
			profile.NumberOfStars = 4
			profile.SubStars = 2
			profile.StarImage = 0
			profile.StarIndicators = 'Worst,Worser,Worse,Mild,Decent,Good,Better,Best'
			profile.Username = request.POST.get('username')
			profile.Email = request.POST.get('email')
			try:
				# Determines if error_text is to be used when complete (ValidationError still raised)
				has_error = False
				error_text = None
				if request.POST.get('password') != request.POST.get('confirm_password'):
					# Checked later to give appropriate error message (Password = '' if user left blank)
					profile.Password = None
				else:
					if request.POST.get('password'):
						password_input = request.POST.get('password')
						# Don't waste time validating rediculously sized password strings
						if len(password_input) >= 1000:
							has_error = True
							error_text = "Password must contain less than 1000 characters."
							raise ValidationError('')
						# Password validation (No unicode, one upper, one lower, one digit, >eight characters)
						text_password = password_input.encode('ascii', 'ignore')
						if len(password_input) != len(text_password):
							has_error = True
							error_text = "Password must contain only letters and digits."
							raise ValidationError('')
						hasUpper, hasLower, hasDigit = False, False, False
						if text_password:
							for char in text_password:
								if char.isalpha():
									if char.isupper():
										hasUpper = True
									elif char.islower():
										hasLower = True
									else:
										has_error = True
										error_text = "Password must contain only letters and digits."
										raise ValidationError('')
								elif char.isdigit:
									hasDigit = True
								else:
									has_error = True
									error_text = "Password must contain only letters and digits"
									raise ValidationError('')
							if not hasUpper or not hasLower or not hasDigit or not len(text_password) >= 8:
								has_error = True
								error_text = "Password must contain at least eight characters (at least: one capital letter, one lowercase letter, one digit)."
								raise ValidationError('')
						# Hash and salt (randomly generated using ALPHABET defined above) password for storage using SHA-256
						salt = ''.join(random.choice(ALPHABET) for i in range(16))
						profile.Password = make_password(text_password, salt, 'pbkdf2_sha256')
					else:
						profile.Password = ''
				# First profile is always an admin
				if Profiles.objects.all().count() == 0:
					profile.IsAdmin = True
				else:
					profile.IsAdmin = False
				profile.full_clean()
				profile.save()
				profile_logger.info(profile.Username + ' Register Success')
				# Login the new profile
				login_command(request, profile)
				profile_logger.info(profile.Username + ' Login Success')
				set_msg(request, 'Welcome ' + profile.Username + '!', 'Your profile has successfully been created.', 3)
				return render_to_response('movie/discovery.html', {'header' : generate_header_dict(request, 'Now What?')}, RequestContext(request))
			# Failed validation (Note for all future cases like this)
			except ValidationError as e:
				# For logging, set to anonymouse if None
				username = profile.Username if profile.Username and profile.Username.encode('ascii', 'replace').isalnum() else 'Anonymous'
				profile_logger.info(username + ' Register Failure')
				error_msg = None
				# Custom error
				if has_error:
					error_msg = {'Password' : error_text}
				# Model validation error (Could be custom, look in model)
				else:
					error_msg = e.message_dict
					if profile.Password == None:
						error_msg['Password'][0] = 'The passwords do not match.'
					# Make string to make pretty on display
					for key in error_msg:
						error_msg[key] = str(error_msg[key][0])
				return render_to_response('profile/registration_form.html', {'header' : generate_header_dict(request, 'Registration'), 'profile' : profile, 'error_msg' : error_msg}, RequestContext(request))
		else:
			'''*****************************************************************************
			Display registration page
			PATH: movies.views.register; METHOD: not post; PARAMS: none; MISC: none;
			*****************************************************************************'''
			return render_to_response('profile/registration_form.html', {'header' : generate_header_dict(request, 'Registration')}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Login user and redirect to appropriate page (access, login, home)
def login(request):
	try:
		if request.method == 'POST':
			'''*****************************************************************************
			Login to profile and redirect to home on success or back to login/access on failure
			PATH: movies.views.login; METHOD: post; PARAMS: none; MISC: none;
			*****************************************************************************'''
			profile = Profiles.objects.get(Username = request.POST.get('username'))
			# Don't waste time validating rediculously sized password strings
			text_password = request.POST.get('password')
			# Use check_password to compare hashed password correctly
			if len(text_password) < 1000 and check_password(text_password, profile.Password):
				login_command(request, profile)
				profile.save()
				profile_logger.info(profile.Username + ' Login Success')
				set_msg(request, 'Welcome back ' + profile.Username + '!', 'You have successfully logged in.', 3)
				return redirect('movies.views.home')
			# Redirect to login if currently logged in (as different profile) or to access otherwise
			else:
				profile_logger.info(profile.Username + ' Login Failure')
				logged_in_profile_id = request.session.get('auth_profile_id')
				logged_in_profile_username = request.session.get('auth_profile_username')
				logged_in_profile_admin = request.session.get('admin')
				if not logged_in_profile_id:
					access = request.session.get('auth_access')
					if not access:
						set_msg(request, 'Login Failed!', 'Username or Password not correct', 4)
						return redirect('movies.views.access')
				return render_to_response('profile/login.html', {'header' : generate_header_dict(request, 'Login'), 'error' : True}, RequestContext(request))
		else:
			'''*****************************************************************************
			Display login page if logged in or have access otherwise back to access
			PATH: movies.views.login; METHOD: not post; PARAMS: none; MISC: none;
			*****************************************************************************'''
			logged_in_profile_id = request.session.get('auth_profile_id')
			logged_in_profile_username = request.session.get('auth_profile_username')
			logged_in_profile_admin = request.session.get('admin')
			if not logged_in_profile_id:
				access = request.session.get('auth_access')
				if not access:
					set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
					return redirect('movies.views.access')
			return render_to_response('profile/login.html', {'header' : generate_header_dict(request, 'Login')}, RequestContext(request))
	except ObjectDoesNotExist:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			access = request.session.get('auth_access')
			if not access:
				set_msg(request, 'Login Failed!', 'Username or Password not correct', 4)
				return redirect('movies.views.access')
		return render_to_response('profile/login.html', {'header' : generate_header_dict(request, 'Login'), 'error' : True}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Logout user and redirect to home
def logout(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		'''*****************************************************************************
		Logout any logged in profile and redirect to home page (five minutes access given)
		PATH: movies.views.logout; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		profile = logout_command(request)
		profile_logger.info(profile.Username + ' Logout Success')
		set_msg(request, 'Tata For Now ' + profile.Username + '!', 'You have successfully logged out.', 4)
		return redirect('movies.views.home')
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display profile list and provied alternative way to registration page
def view_profiles(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		# Note genre and person don't have this option because they have to be added through a movie to be relevant
		if request.GET.get('add'):
			'''*****************************************************************************
			Display registration page
			PATH: movies.views.view_profiles; METHOD: none; PARAMS: get - add; MISC: none;
			*****************************************************************************'''
			return render_to_response('profile/registration_form.html', {'header' : generate_header_dict(request, 'Registration')}, RequestContext(request))
		else:
			'''*****************************************************************************
			Display profile list page
			PATH: movies.views.view_profiles; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			profiles = None
			profile_list = Profiles.objects.all().order_by('Username')
			# Standard pagination code that will be seen later as well, default length to 25, max of 100
			length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 25
			length = length if length <= 100 else 100
			paginator = Paginator(profile_list, length)
			page = request.GET.get('page')
			try:
				profiles = paginator.page(page)
			# Default to first page
			except PageNotAnInteger:
				profiles = paginator.page(1)
			# Fallback to last page
			except EmptyPage:
				profiles = paginator.page(paginator.num_pages)
			return render_to_response('profile/view_list.html', {'header' : generate_header_dict(request, 'Profile List'), 'profiles' : profiles}, RequestContext(request))
	except Exception:
		profile_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Profile tools including view, edit, delete, drag and drop rankings, view associated movies, and suggestions
def view_profile(request, username):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		profile = Profiles.objects.get(Username=username)
		admin_rights = logged_in_profile_id and (logged_in_profile_id == profile.id or logged_in_profile_admin)
		if logged_in_profile_id == profile.id and request.GET.get('rank'):
			if request.method == 'POST' and request.POST.get('hiddenMovieIds'):
				'''*****************************************************************************
				Save drag and drop ranks and redirect back to drag and drop page
				PATH: movies.views.view_profile username; METHOD: post; PARAMS: get/post - rank/hiddenMovieIds; MISC: logged_in_profile_username = username;
				*****************************************************************************'''
				movies, unranked_movies = [], []
				ids_string = request.POST.get('hiddenMovieIds')
				ids = ids_string.split(',')
				# For each movie in order of ranking on page
				for i in range(len(ids)):
					# If unranked, ignore
					if ids[i][0] == "u":
						try:
							associated_movie = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = ids[i][1:])
							unranked_movies.append(associated_movie.MovieId)
						except Exception:
							continue
					# Else, update rank according to iteration number of for loop
					else:
						try:
							associated_movie = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = ids[i])
							if associated_movie.Rank != i+1:
								associated_movie.Rank = i+1
								associated_movie.save()
								associate_logger.info(associated_movie.MovieId.UrlTitle + ' Update rank to ' + str(associated_movie.Rank) + ' Success')
							movies.append(associated_movie.MovieId)
						except Exception:
							continue
				size = len(movies) + len(unranked_movies)
				set_msg(request, 'Rankings Updated!', 'Your rankings have successfully been updated.', 3)
				return render_to_response('profile/dnd_rank.html', {'header' : generate_header_dict(request, profile.Username + '\'s Rankings'), 'profile' : profile, 'movies' : movies, 'unranked_movies' : unranked_movies, size : size}, RequestContext(request))
			else:
				'''*****************************************************************************
				Display drag and drop page
				PATH: movies.views.view_profile username; METHOD: not post; PARAMS: get - rank; MISC: logged_in_profile_username = username;
				*****************************************************************************'''
				movies, unranked_movies = [], []
				# Get all ranked and unranked (watched with no rank) titles and sort by rank followed by descending year followed by title (same for almost all lists)
				associated_movies = ProfileMovies.objects.select_related().filter(ProfileId = logged_in_profile_id, Watched = True).order_by('Rank', '-MovieId__Year', 'MovieId__Title')
				for assoc in associated_movies:
					if assoc.Rank:
						movies.append(assoc.MovieId)
					else:
						unranked_movies.append(assoc.MovieId)
				size = len(movies) + len(unranked_movies)
				return render_to_response('profile/dnd_rank.html', {'header' : generate_header_dict(request, profile.Username + '\'s Rankings'), 'profile' : profile, 'movies' : movies, 'unranked_movies' : unranked_movies, size : size}, RequestContext(request))
		elif request.GET.get('movies'):
			'''*****************************************************************************
			Display all movies associated with a profile
			PATH: movies.views.view_profile username; METHOD: none; PARAMS: get - movies; MISC: none;
			*****************************************************************************'''
			movies, unranked_movies, unseen_movies = [], [], []
			associated_movies = ProfileMovies.objects.select_related().filter(ProfileId = logged_in_profile_id).order_by('Rank', '-MovieId__Year', 'MovieId__Title')
			for assoc in associated_movies:
				if assoc.Rank:
					movies.append(assoc.MovieId)
				elif assoc.Watched:
					unranked_movies.append(assoc.MovieId)
				else:
					unseen_movies.append(assoc.MovieId)
			return render_to_response('profile/movies.html', {'header' : generate_header_dict(request, profile.Username + '\'s Movies'), 'profile' : profile, 'movies' : movies, 'unranked_movies' : unranked_movies, 'unseen_movies' : unseen_movies}, RequestContext(request))
		elif request.GET.get('suggestion'):
			if request.method == 'POST':
				'''*****************************************************************************
				Send suggestion/comment/correction email and redirect to profile page
				PATH: movies.views.view_profile username; METHOD: post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id)
				email_message = request.POST.get('message') if request.POST.get('message') else None
				set_msg(request, 'Thank you for your feedback!', 'We have recieved your suggestion/comment/correction and will react to it appropriately.', 3)
				if email_message:
					# send email
					pass
				else:
					pass
				return redirect('movies.views.view_profile', username=profile.Username)
			else:
				'''*****************************************************************************
				Display suggestion/comment/correction page
				PATH: movies.views.view_profile username; METHOD: not post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				return render_to_response('suggestion_form.html', {'header' : generate_header_dict(request, 'Suggestion/Comment/Correction'), 'profile' : profile}, RequestContext(request))
		elif admin_rights and request.GET.get('edit'):
			if request.method == 'POST':
				'''*****************************************************************************
				Save changes made to profile and redirect to profile page
				PATH: movies.views.view_profile username; METHOD: post; PARAMS: get - edit; MISC: logged_in_profile_username = username OR logged_in_profile.IsAdmin;
				*****************************************************************************'''
				has_error = False
				error_text = None
				try:
					profile = Profiles.objects.get(Username=username)
					profile.Username = request.POST.get('username')
					profile.Email = request.POST.get('email')
					old_num = profile.NumberOfStars
					profile.NumberOfStars = request.POST.get('star_numbers')
					old_sub = profile.SubStars
					profile.SubStars = request.POST.get('substars')
					profile.StarImage = request.POST.get('stars')
					profile.full_clean()
					same_sub = old_sub == profile.SubStars and old_num == profile.NumberOfStars
					indicators = ""
					# Set indicators to text if no change in number of indicators otherwise set to arbitrary numbers (0.5, 1.0, ...)
					for i in range(profile.NumberOfStars):
						for j in range(profile.SubStars):
							lookup = 'indicator_' + str(i) + '_' + str(j)
							if same_sub and request.POST.get(lookup):
								indicators += request.POST.get(lookup)
							else:
								indicators += str(i + (float(j + 1) / profile.SubStars))
							indicators += ','
					profile.StarIndicators = indicators[0:-1]
					if logged_in_profile_admin:
						profile.IsAdmin = request.POST.get('admin') == 'IsAdmin'
					# Validate password same as before
					if len(request.POST.get('password')) >= 1000:
						has_error = True
						error_text = "Password must contain less than 1000 characters."
						raise ValidationError('')
					if request.POST.get('password') == '':
						pass
					elif request.POST.get('password') != request.POST.get('confirm_password'):
						profile.Password = None
					else:
						if request.POST.get('password'):
							password_input = request.POST.get('password')
							if len(password_input) != len(text_password):
								has_error = True
								error_text = "Password must contain only letters and digits."
								raise ValidationError('')
							text_password = password_input.encode('ascii', 'ignore')
							hasUpper, hasLower, hasDigit = False, False, False
							if text_password:
								for char in text_password:
									if char.isalpha():
										if char.isupper():
											hasUpper = True
										elif char.islower():
											hasLower = True
										else:
											has_error = True
											error_text = "Password must contain only letters and digits."
											raise ValidationError('')
									elif char.isdigit:
										hasDigit = True
									else:
										has_error = True
										error_text = "Password must contain only letters and digits"
										raise ValidationError('')
								if not hasUpper or not hasLower or not hasDigit or not len(text_password) >= 8:
									has_error = True
									error_text = "Password must contain at least eight characters (at least: one capital letter, one lowercase letter, one digit)."
									raise ValidationError('')
							salt = ''.join(random.choice(ALPHABET) for i in range(16))
							profile.Password = make_password(text_password, salt, 'pbkdf2_sha256')
						else:
							profile.Password = ''
					profile.full_clean()
					profile.save()
					profile_logger.info(profile.Username + ' Update Success by ' + logged_in_profile_username)
					set_msg(request, 'Profile Updated!', 'Your profile has successfully been updated.', 3)
					return redirect('movies.views.view_profile', username=profile.Username)
				except ValidationError as e:
					username = profile.Username if profile.Username and profile.Username.encode('ascii', 'replace').isalnum() else 'Anonymous'
					profile_logger.info(username + ' Register Failure')
					error_msg = None
					if has_error:
						error_msg = {'Password' : error_text}
					else:
						error_msg = e.message_dict
						if profile.Password == None:
							error_msg['Password'][0] = 'The passwords do not match.'
						for key in error_msg:
							error_msg[key] = str(error_msg[key][0])
					# Used in displaying default rating in starbox (see template)
					rate_range = []
					for i in range(profile.NumberOfStars):
						for j in range(profile.SubStars):
							rate_range.append(str(i + (float(j + 1) / profile.SubStars)))
					return render_to_response('profile/edit.html', {'header' : generate_header_dict(request, 'Settings'), 'profile' : profile, 'indicators' : profile.StarIndicators.split(','), 'rate_range' : rate_range, 'error_msg' : error_msg}, RequestContext(request))
			else:
				'''*****************************************************************************
				Display edit page
				PATH: movies.views.view_profile username; METHOD: not post; PARAMS: get - edit; MISC: logged_in_profile_username = username OR logged_in_profile.IsAdmin;
				*****************************************************************************'''
				rate_range = []
				for i in range(profile.NumberOfStars):
					for j in range(profile.SubStars):
						rate_range.append(str(i + (float(j + 1) / profile.SubStars)))
				return render_to_response('profile/edit.html', {'header' : generate_header_dict(request, 'Settings'), 'profile' : profile, 'indicators' : profile.StarIndicators.split(','), 'rate_range' : rate_range}, RequestContext(request))
		elif admin_rights and request.GET.get('delete'):
			'''*****************************************************************************
			Delete profile and redirect to home
			PATH: movies.views.view_profile username; METHOD: none; PARAMS: get - delete; MISC: logged_in_profile_username = username OR logged_in_profile.IsAdmin;
			*****************************************************************************'''
			if logged_in_profile_id == profile.id:
				prof = logout_command(request)
				profile_logger.info(prof.Username + ' Logout Success')
			# Delete all associations
			ProfileMovies.objects.filter(ProfileId=profile).delete()
			profile.delete()
			profile_logger.info(profile.Username + ' Delete Success by ' + logged_in_profile_username)
			set_msg(request, 'Goodbye ' + profile.Username + '!', 'Your profile has successfully been deleted.', 5)
			return redirect('movies.views.home')
		else:
			'''*****************************************************************************
			Display profile page
			PATH: movies.views.view_profile username; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			indicators = profile.StarIndicators.split(',')
			return render_to_response('profile/view.html', {'header' : generate_header_dict(request, profile.Username), 'profile' : profile, 'admin_rights' : admin_rights, 'indicators' : indicators}, RequestContext(request))
	except ObjectDoesNotExist:
		raise Http404
	except Exception:
		profile_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display movie list and tools for admin add movie, and add movie with ids
def view_movies(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		if request.GET.get('add') and request.GET.get('i') and request.GET.get('r') and request.GET.get('n'):
			'''*****************************************************************************
			Add movie given imdb identifier (rotten tomatoes and netflix ids required as well) and redirect to movie page by way of association functions if success otherwise back to search with errors
			PATH: movies.views.view_movies; METHOD: none; PARAMS: get - add,i,r,n; MISC: none;
			*****************************************************************************'''
			res_dict = movie_from_imdb_input(request.GET.get('i'))
			if res_dict.get('movie'):
				try:
					has_error = False
					error_text = None
					movie = res_dict.get('movie')
					# Get wikipedia id if resolved
					res = wikipedia_id_from_movie(movie)
					if res.get('id'):
						movie.WikipediaId = res.get('id')
					# Set rotten tomatoes and netflix id
					movie.RottenTomatoesId = request.GET.get('r')
					test = movie_from_rottentomatoes_input(movie.RottenTomatoesId)
					if not test.get('movie'):
						has_error = True
						error_text = 'Rotten Tomatoes ID Validation: ' + test.get('error_msg')
						raise ValidationError('')
					movie.NetflixId = request.GET.get('n')
					test = movie_from_netflix_input(movie.NetflixId)
					if not test.get('movie'):
						has_error = True
						error_text = 'Netflix ID Validation: ' + test.get('error_msg')
						raise ValidationError('')
					movie.full_clean()
					movie.save()
					create_properties(movie, res_dict.get('directors'), res_dict.get('writers'), res_dict.get('actors'), res_dict.get('genres'), logged_in_profile_username)
					movie_logger.info(movie.UrlTitle + ' Create Success by '  + logged_in_profile_username)
					# Redirect to 'add association' function and add option 'seen' if already present
					res = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					res['Location'] += '?assoc=1&add=1'
					if request.GET.get('seen'):
						res['Location'] += '&seen=1'
					return res
				except ValidationError as e:
					urltitle = movie.UrlTitle if movie.UrlTitle else 'Unknown'
					movie_logger.info(urltitle + ' Create Failure by ' + logged_in_profile_username)
					error_msg = None
					if has_error:
						error_msg = {'Error' : error_text}
					else:
						error_msg = e.message_dict
						for key in error_msg:
							error_msg[key] = str(error_msg[key][0])
					return render_to_response('movie/search.html', {'header' : generate_header_dict(request, 'Search'), 'success' : False, 'results' : error_msg}, RequestContext(request))
			else:
				movie_logger.info('Movie Create Failure by ' + logged_in_profile_username)
				res_dict['error_list'] = {'ImdbId' : res_dict['error_msg']}
				return render_to_response('movie/search.html', {'header' : generate_header_dict(request, 'Search'), 'success' : False, 'results' : res_dict.get('error_list')}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('add'):
			if request.method == 'POST':
				'''*****************************************************************************
				Add movie given user input of imdb, netflix, and rottentomatoes urls or ids and redirect to movie page
				PATH: movies.views.view_movies; METHOD: post; PARAMS: get - add; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				res_dict = movie_from_inputs(request.POST.get('imdb_url'), request.POST.get('netflix_url'), request.POST.get('rottentomatoes_id'))
				if res_dict.get('success'):
					try:
						movie = res_dict.get('movie')
						movie.full_clean()
						movie.save()
						create_properties(movie, res_dict.get('directors'), res_dict.get('writers'), res_dict.get('actors'), res_dict.get('genres'), logged_in_profile_username)
						movie_logger.info(movie.UrlTitle + ' Create Success by ' + logged_in_profile_username)
						return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					except ValidationError as e:
						urltitle = movie.UrlTitle if movie.UrlTitle else 'Unknown'
						movie_logger.info(urltitle + ' Create Failure by ' + logged_in_profile_username)
						error_msg = e.message_dict
						for key in error_msg:
							error_msg[key] = str(error_msg[key][0])
						return render_to_response('movie/add.html', {'header' : generate_header_dict(request, 'Add Movie'), 'error_msg' : error_msg, 'movie' : movie, 'imdb_link' : imdb_link_for_movie(movie), 'rt_link' : rottentomatoes_link_for_movie(movie), 'netflix_link' : netflix_link_for_movie(movie), 'wikipedia_link' : wikipedia_link_for_movie(movie)}, RequestContext(request))
				else:
					movie_logger.info('Movie Create Failure by ' + logged_in_profile_username)
					return render_to_response('movie/add.html', {'header' : generate_header_dict(request, 'Add Movie'), 'movie' : res_dict.get('movie'), 'error_msg' : res_dict.get('error_list'), 'imdb_link' : imdb_link_for_movie(res_dict.get('movie')), 'rt_link' : rottentomatoes_link_for_movie(res_dict.get('movie')), 'netflix_link' : netflix_link_for_movie(res_dict.get('movie')), 'wikipedia_link' : wikipedia_link_for_movie(res_dict.get('movie'))}, RequestContext(request))
			else:
				'''*****************************************************************************
				Display admin add movie page
				PATH: movies.views.view_movies; METHOD: none; PARAMS: get - add; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				return render_to_response('movie/add.html', {'header' : generate_header_dict(request, 'Add Movie')}, RequestContext(request))
		else:
			'''*****************************************************************************
			Display movie list page
			PATH: movies.views.view_movies; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			movies, paginated_movies = [], None
			movie_list = Movies.objects.all().order_by('-Year', 'Title')
			length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 25
			length = length if length <= 100 else 100
			paginator = Paginator(movie_list, length)
			page = request.GET.get('page')
			try:
				paginated_movies = paginator.page(page)
			except PageNotAnInteger:
				paginated_movies = paginator.page(1)
			except EmptyPage:
				paginated_movies = paginator.page(paginator.num_pages)
			# Get all associations with logged in profile to correctly display links (seen later as well)
			for movie in paginated_movies:
				try:
					association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
					movies.append((movie, True))
				except Exception:
					movies.append((movie, False))
			return render_to_response('movie/view_list.html', {'header' : generate_header_dict(request, 'Movie List'), 'movies' : movies, 'page' : paginated_movies}, RequestContext(request))
	except Exception:
		movie_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Movie tools including view, rank, delete, edit, suggestion, add property, and association tools (add, remove, edit)
def view_movie(request, urltitle):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		movie = Movies.objects.get(UrlTitle=urltitle)
		# Get all properties associated with movie (actors, directors, writers, genres)
		properties = MovieProperties.objects.filter(MovieId=movie)
		directors, writers, actors, genres = [], [], [], []
		for property in properties:
			if property.Type == 0:
				directors.append(People.objects.get(id=property.PropertyId))
			elif property.Type == 1:
				writers.append(People.objects.get(id=property.PropertyId))
			elif property.Type == 2:
				actors.append(People.objects.get(id=property.PropertyId))
			elif property.Type == 3:
				genres.append(Genres.objects.get(id=property.PropertyId))
		if request.GET.get('assoc'):
			try:
				if request.GET.get('add'):
					'''*****************************************************************************
					Create association and redirect to movie page
					PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - assoc,add; MISC: none;
					*****************************************************************************'''
					watched = True if request.GET.get('seen') else False
					profile = Profiles.objects.get(id=logged_in_profile_id)
					profile_movie = ProfileMovies(ProfileId = profile, MovieId = movie, Watched = watched, Accessible = False, CreatedAt = datetime.now(), UpdatedAt = datetime.now())
					profile_movie.save()
					associate_logger.info(profile.Username + ' Associated ' + movie.UrlTitle + ' Success')
					set_msg(request, 'Movie Associated!', movie.Title + ' has been added to your list of movies.', 3)
				elif request.GET.get('recent'):
					'''*****************************************************************************
					Update association based on a user recently watching a movie and redirect to movie page
					PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - assoc,recent; MISC: none;
					*****************************************************************************'''
					association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
					# First time watching the specified movie
					if not association.Watched:
						association.Watched = True
						association.CreatedAt = datetime.now()
					association.UpdatedAt = datetime.now()
					association.save()
					associate_logger.info(logged_in_profile_username + ' Association with ' + movie.UrlTitle + ' Update Success')
					set_msg(request, 'Association Updated!', 'Your association with ' + movie.Title + ' has successfully been updated.', 3)
				elif request.method == 'POST' and request.GET.get('update'):
					'''*****************************************************************************
					Update association from input on movie page and redirect to movie page
					PATH: movies.views.view_movie urltitle; METHOD: post; PARAMS: get - assoc,update; MISC: none;
					*****************************************************************************'''
					rankings_changed = False
					association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
					# If first time user watched the movie, update both times
					if not association.Watched and request.POST.get('watched') == 'Watched':
						association.CreatedAt = datetime.now()
						association.UpdatedAt = datetime.now()
					# Else if user is unwatching movie (should not be allowed but mistakes happen), delete saved data
					elif association.Watched and not request.POST.get('watched') == 'Watched':
						association.CreatedAt = None
						association.UpdatedAt = None
						association.Rank = None
						association.Rating = None
						association.Review = None
						rankings_changed = True
					# Update the rest
					else:
						if request.POST.get('rating_rated') and request.POST.get('rating_rated') != 'false':
							# Handle bad float coercion by ignoring the rating
							try:
								profile = Profiles.objects.get(id=logged_in_profile_id)
								association.Rating = int(math.ceil(float(request.POST.get('rating_rated')) * int(math.ceil(100 / profile.NumberOfStars))))
							except Exception:
								pass
						association.Review = request.POST.get('review')
					association.Watched = request.POST.get('watched') == 'Watched'
					# Clear source information if no longer accessiblt
					if association.Accessible and not request.POST.get('accessible') == 'Accessible':
						association.Source = None
					else:
						association.Source = request.POST.get('source')
					association.Accessible = request.POST.get('accessible') == 'Accessible'
					association.save()
					associate_logger.info(logged_in_profile_username + ' Association with ' + movie.UrlTitle + ' Update Success')
					# Update rankings if ranking of movie was altered in some way
					if rankings_changed:
						update_rankings(logged_in_profile_id)
					set_msg(request, 'Association Updated!', 'Your association with ' + movie.Title + ' has successfully been updated.', 3)
				elif request.GET.get('remove'):
					'''*****************************************************************************
					Delete association and redirect to movie page
					PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - assoc,remove; MISC: none;
					*****************************************************************************'''
					association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
					association.delete()
					associate_logger.info(logged_in_profile_username + ' Disassociated ' + movie.UrlTitle + ' Success')
					# Fill in the gap in rankings
					update_rankings(logged_in_profile_id)
					set_msg(request, 'Movie Disassociated!', movie.Title + ' has been removed from your list of movies.', 5)
			except ObjectDoesNotExist:
				set_msg(request, 'Association Not Found!', 'You have no association with ' + movie.Title + '.', 5)
			except Exception:
				associate_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
				return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))
			return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
		elif request.GET.get('rank'):
			try:
				profile = Profiles.objects.get(id=logged_in_profile_id)
				association = ProfileMovies.objects.get(ProfileId = profile, MovieId = movie, Watched=True)
				association.Rating = association.Rating / float(math.ceil(100 / profile.NumberOfStars)) if association.Rating else None
				associations = ProfileMovies.objects.filter(ProfileId=profile,Watched=True).exclude(Rank__isnull=True).order_by('Rank')
				if request.method == 'POST':
					'''*****************************************************************************
					Tree ranker substep consisting of providing new comparison if not done ranking or redirecting to movie page if done ranking
					PATH: movies.views.view_movie urltitle; METHOD: post; PARAMS: get - rank; MISC: none;
					*****************************************************************************'''
					# Min and max limits (done when max < min)
					min = int(request.POST.get('hiddenMin')) if request.POST.get('hiddenMin') and request.POST.get('hiddenMin').isdigit() else 0
					max = int(request.POST.get('hiddenMax')) if request.POST.get('hiddenMax') and request.POST.get('hiddenMax').isdigit() else -1
					mid = (min + max) / 2
					# If done
					if max < min:
						old_rank = int(request.POST.get('hiddenPickOld')) if request.POST.get('hiddenPickOld') and request.POST.get('hiddenPickOld').isdigit() else 0
						new_rank = int(request.POST.get('hiddenPickNew')) if request.POST.get('hiddenPickNew') and request.POST.get('hiddenPickNew').isdigit() else 0
						start = None
						# If picked old movie, start reranking (including new rank) at one above old movie
						if old_rank:
							start = old_rank + 1
						# If picked new movie, start reranking at new_rank (usually one less than old_rank)
						elif new_rank:
							start = new_rank
						association.Rank = start
						# Increment all ranks above new one (can't use update_rankings because of single duplicate rank)
						for assoc in associations:
							if assoc.Rank >= start:
								assoc.Rank += 1
								assoc.save()
						association.save()
						associate_logger.info(logged_in_profile_username + ' Association with ' + movie.UrlTitle + ' Rank: ' + str(association.Rank) + ' Success')
						set_msg(request, 'Movie Ranked!', movie.Title + ' is ranked number ' + str(association.Rank) + ' out of ' + str(associations.count() + 1) + '.', 3)
						return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					# Else continue ranking by finding new comparison movie
					else:
						compare_movie = Movies.objects.get(id=associations[mid].MovieId.id)
						compare_association = ProfileMovies.objects.get(ProfileId = profile, MovieId = compare_movie)
						compare_association.Rating = compare_association.Rating / float(math.ceil(100 / profile.NumberOfStars)) if compare_association.Rating else None
						# Use in deciding to pick which movie on left or right
						rand = random.randint(0,1)
						return render_to_response('movie/t_rank.html', {'header' : generate_header_dict(request, 'Movie Ranker'), 'movie' : movie, 'movie1' : movie if rand == 0 else compare_movie, 'movie2' : compare_movie if rand == 0 else movie, 'association1' : association if rand == 0 else compare_association, 'association2' : compare_association if rand == 0 else association, 'min1' : min if rand == 0 else mid + 1, 'max1' : mid - 1 if rand == 0 else max, 'min2' : mid + 1 if rand == 0 else min, 'max2' : max if rand == 0 else mid - 1}, RequestContext(request))
				else:
					'''*****************************************************************************
					Tree ranker start displaying first comparison or redirecting to movie page if first ranking
					PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - rank; MISC: none;
					*****************************************************************************'''
					if associations.count() == 0:
						association.Rank = 1
						association.save()
						associate_logger.info(logged_in_profile_username + ' Association with ' + movie.UrlTitle + ' Rank: ' + str(association.Rank) + ' Success')
						set_msg(request, 'Movie Ranked!', movie.Title + ' is ranked number 1 out of 1.', 3)
						return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					min = 0
					max = associations.count()-1
					mid = (min + max) / 2
					compare_movie = Movies.objects.get(id=associations[mid].MovieId.id)
					compare_association = ProfileMovies.objects.get(ProfileId = profile, MovieId = compare_movie)
					compare_association.Rating = compare_association.Rating / float(math.ceil(100 / profile.NumberOfStars)) if compare_association.Rating else None
					rand = random.randint(0,1)
					return render_to_response('movie/t_rank.html', {'header' : generate_header_dict(request, 'Movie Ranker'), 'movie' : movie, 'movie1' : movie if rand == 0 else compare_movie, 'movie2' : compare_movie if rand == 0 else movie, 'association1' : association if rand == 0 else compare_association, 'association2' : compare_association if rand == 0 else association, 'min1' : min if rand == 0 else mid + 1, 'max1' : mid - 1 if rand == 0 else max, 'min2' : mid + 1 if rand == 0 else min, 'max2' : max if rand == 0 else mid - 1}, RequestContext(request))
			except Exception:
				return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
		elif request.GET.get('rerank'):
			'''*****************************************************************************
			Remove current rank if present and redirect to tree ranker regardless
			PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - rank; MISC: none;
			*****************************************************************************'''
			try:
				association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
				association.Rank = None
				association.save()
				associate_logger.info(logged_in_profile_username + ' Association with ' + movie.UrlTitle + ' Reset Rank Success')
				update_rankings(logged_in_profile_id)
			except Exception:
				pass
			res = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
			res['Location'] += '?rank=1'
			return res
		elif request.GET.get('suggestion'):
			if request.method == 'POST':
				'''*****************************************************************************
				Send suggestion/comment/correction email and redirect to movie page
				PATH: movies.views.view_movie urltitle; METHOD: post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				profile = Profiles.objects.get(id=logged_in_profile_id)
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id) + ' MovieId: ' + str(movie.id)
				email_message = request.POST.get('message') if request.POST.get('message') else None
				set_msg(request, 'Thank you for your feedback!', 'We have recieved your suggestion/comment/correction and will react to it appropriately.', 3)
				if email_message:
					# send email
					pass
				else:
					pass
				return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
			else:
				'''*****************************************************************************
				Display suggestion/comment/correction page
				PATH: movies.views.view_movie urltitle; METHOD: not post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				return render_to_response('suggestion_form.html', {'header' : generate_header_dict(request, 'Suggestion/Comment/Correction'), 'movie' : movie}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('edit'):
			if request.method == 'POST':
				'''*****************************************************************************
				Save changes made to movie and redirect to movie page
				PATH: movies.views.view_movie urltitle; METHOD: post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				try:
					movie.Title = request.POST.get('title')
					movie.Year = request.POST.get('year')
					movie.Runtime = request.POST.get('runtime')
					movie.ImdbId = request.POST.get('imdb')
					movie.RottenTomatoesId = request.POST.get('rottentomatoes')
					movie.NetflixId = request.POST.get('netflix')
					movie.full_clean()
					movie.save()
					movie_logger.info(movie.UrlTitle + ' Update Success by ' + logged_in_profile_username)
					set_msg(request, 'Movie Updated!', movie.Title + ' has successfully been updated.', 3)
					return redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
				except ValidationError as e:
					movie_logger.info(movie.UrlTitle + ' Update Failure by ' + logged_in_profile_username)
					error_msg = e.message_dict
					for key in error_msg:
						error_msg[key] = str(error_msg[key][0])
					return render_to_response('movie/edit.html', {'header' : generate_header_dict(request, 'Update'), 'movie' : movie, 'directors' : directors, 'writers' : writers, 'actors' : actors, 'genres' : genres, 'imdb_link' : imdb_link_for_movie(movie), 'rt_link' : rottentomatoes_link_for_movie(movie), 'netflix_link' : netflix_link_for_movie(movie), 'wikipedia_link' : wikipedia_link_for_movie(movie), 'error_msg' : error_msg, 'people_list' : map(str, People.objects.values_list('Name', flat=True).order_by('Name')), 'genres_list' : map(str, Genres.objects.values_list('Description', flat=True).order_by('Description'))}, RequestContext(request))
			elif logged_in_profile_admin and request.GET.get('delete'):
				'''*****************************************************************************
				Delete movie and redirect to home
				PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: get - delete; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				for property in properties:
					person, genre = None, None
					# Delete all property associations
					property.delete()
					# Delete actual property if no longer relevant (i.e. has another movie associated with it)
					if property.Type == 0 or property.Type == 1 or property.Type == 2:
						person = People.objects.get(id=property.PropertyId)
						if person_is_relevant(person):
							continue
						else:
							person.delete()
							property_logger.info(person.UrlName + ' Delete Success by ' + logged_in_profile_username)
					elif property.Type == 3:
						genre = Genres.objects.get(id=property.PropertyId)
						if genre_is_relevant(genre):
							continue
						else:
							genre.delete()
							property_logger.info(genre.Description + ' Delete Success by ' + logged_in_profile_username)
				# Delete all profile associations (Update rankings afterwards)
				for association in ProfileMovies.objects.select_related().filter(MovieId=movie):
					association.delete()
					associate_logger.info(logged_in_profile_username + ' Disassociated ' + movie.UrlTitle + ' Success')
					update_rankings(association.ProfileId)
				# Delete movie
				movie.delete()
				movie_logger.info(movie.UrlTitle + ' Delete Success by ' + logged_in_profile_username)
				set_msg(request, 'Movie Deleted!', movie.Title + ' has successfully been deleted.', 5)
				return redirect('movies.views.home')
			else:
				'''*****************************************************************************
				Display edit page
				PATH: movies.views.view_movie urltitle; METHOD: not post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				return render_to_response('movie/edit.html', {'header' : generate_header_dict(request, 'Update'), 'movie' : movie, 'directors' : directors, 'writers' : writers, 'actors' : actors, 'genres' : genres, 'imdb_link' : imdb_link_for_movie(movie), 'rt_link' : rottentomatoes_link_for_movie(movie), 'netflix_link' : netflix_link_for_movie(movie), 'wikipedia_link' : wikipedia_link_for_movie(movie), 'people_list' : map(str, People.objects.values_list('Name', flat=True).order_by('Name')), 'genres_list' : map(str, Genres.objects.values_list('Description', flat=True).order_by('Description'))}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('add') and request.method == 'POST':
			'''*****************************************************************************
			Create property association with movie and redirect to edit page
			PATH: movies.views.view_movie urltitle; METHOD: post; PARAMS: get - add; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			dirs, writs, acts, gens = [], [], [], []
			type = int(request.GET.get('t')) if request.GET.get('t') and request.GET.get('t').isdigit() else -1
			value = request.POST.get('add')
			if type ==  0:
				dirs.append(value)
			elif type == 1:
				writs.append(value)
			elif type == 2:
				acts.append(value)
			elif type == 3:
				gens.append(value)
			create_properties(movie, dirs, writs, acts, gens, logged_in_profile_username)
			set_msg(request, 'Property Added!', movie.Title + ' has successfully been updated with the new property specified.', 3)
			properties = MovieProperties.objects.filter(MovieId=movie)
			directors, writers, actors, genres = [], [], [], []
			for property in properties:
				if property.Type == 0:
					directors.append(People.objects.get(id=property.PropertyId))
				elif property.Type == 1:
					writers.append(People.objects.get(id=property.PropertyId))
				elif property.Type == 2:
					actors.append(People.objects.get(id=property.PropertyId))
				elif property.Type == 3:
					genres.append(Genres.objects.get(id=property.PropertyId))
			return render_to_response('movie/edit.html', {'header' : generate_header_dict(request, 'Update'), 'movie' : movie, 'directors' : directors, 'writers' : writers, 'actors' : actors, 'genres' : genres, 'imdb_link' : imdb_link_for_movie(movie), 'rt_link' : rottentomatoes_link_for_movie(movie), 'netflix_link' : netflix_link_for_movie(movie), 'wikipedia_link' : wikipedia_link_for_movie(movie), 'people_list' : map(str, People.objects.values_list('Name', flat=True).order_by('Name')), 'genres_list' : map(str, Genres.objects.values_list('Description', flat=True).order_by('Description'))}, RequestContext(request))
		else:
			'''*****************************************************************************
			Display movie page
			PATH: movies.views.view_movie urltitle; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			association = None
			profile = None
			indicators = []
			try:
				profile = Profiles.objects.get(id=logged_in_profile_id)
				association = ProfileMovies.objects.get(ProfileId = profile, MovieId = movie)
				# Scale rating to profile preference (i.e. from percentage rating to n stars)
				association.Rating = association.Rating / float(math.ceil(100 / profile.NumberOfStars)) if association.Rating else None
				indicators = profile.StarIndicators.split(',')
			except Exception:
				pass
			return render_to_response('movie/view.html', {'header' : generate_header_dict(request, movie.Title + ' (' + str(movie.Year) + ')'), 'movie' : movie, 'profile' : profile, 'indicators' : indicators, 'association' : association, 'directors' : directors, 'writers' : writers, 'actors' : actors, 'genres' : genres, 'imdb_link' : imdb_link_for_movie(movie), 'rt_link' : rottentomatoes_link_for_movie(movie), 'netflix_link' : netflix_link_for_movie(movie), 'wikipedia_link' : wikipedia_link_for_movie(movie)}, RequestContext(request))
	except ObjectDoesNotExist:
		raise Http404
	except Exception:
		movie_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display people list
def view_people(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		'''*****************************************************************************
		Display people list page
		PATH: movies.views.view_people; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		people = None
		people_list = People.objects.all().order_by('Name')
		length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 25
		length = length if length <= 100 else 100
		paginator = Paginator(people_list, length)
		page = request.GET.get('page')
		try:
			people = paginator.page(page)
		except PageNotAnInteger:
			people = paginator.page(1)
		except EmptyPage:
			people = paginator.page(paginator.num_pages)
		return render_to_response('property/view_people_list.html', {'header' : generate_header_dict(request, 'People List'), 'people' : people}, RequestContext(request))
	except Exception:
		property_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Person tools including view, delete, edit, suggestion, and movie association tools (add, remove)
def view_person(request, urlname):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		# Get all movie associations with person and get all profile associations with said movies
		person = People.objects.get(UrlName=urlname)
		directed_properties = MovieProperties.objects.select_related().filter(Type=0, PropertyId=person.id).order_by('-MovieId__Year', 'MovieId__Title')
		written_properties = MovieProperties.objects.select_related().filter(Type=1, PropertyId=person.id).order_by('-MovieId__Year', 'MovieId__Title')
		acted_properties = MovieProperties.objects.select_related().filter(Type=2, PropertyId=person.id).order_by('-MovieId__Year', 'MovieId__Title')
		directed_movies, written_movies, acted_movies = [], [], []
		directed_movies_tuples, written_movies_tuples, acted_movies_tuples = [], [], []
		for prop in directed_properties:
			movie = prop.MovieId
			directed_movies.append(movie)
			try:
				association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
				directed_movies_tuples.append((movie, True))
			except Exception:
				directed_movies_tuples.append((movie, False))
		for prop in written_properties:
			movie = prop.MovieId
			written_movies.append(movie)
			try:
				association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
				written_movies_tuples.append((movie, True))
			except Exception:
				written_movies_tuples.append((movie, False))
		for prop in acted_properties:
			movie = prop.MovieId
			acted_movies.append(movie)
			try:
				association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
				acted_movies_tuples.append((movie, True))
			except Exception:
				acted_movies_tuples.append((movie, False))
		if request.GET.get('suggestion'):
			if request.method == 'POST':
				'''*****************************************************************************
				Send suggestion/comment/correction email and redirect to person page
				PATH: movies.views.view_person urlname; METHOD: post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				profile = Profiles.objects.get(id=logged_in_profile_id)
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id) + ' PersonId: ' + str(person.id)
				email_message = request.POST.get('message') if request.POST.get('message') else None
				set_msg(request, 'Thank you for your feedback!', 'We have recieved your suggestion/comment/correction and will react to it appropriately.', 3)
				if email_message:
					# send email
					pass
				else:
					pass
				return redirect('movies.views.view_person', urlname=person.UrlName)
			else:
				'''*****************************************************************************
				Display suggestion/comment/correction page
				PATH: movies.views.view_person urlname; METHOD: not post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				return render_to_response('suggestion_form.html', {'header' : generate_header_dict(request, 'Suggestion/Comment/Correction'), 'person' : person}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('edit'):
			if request.method == 'POST':
				'''*****************************************************************************
				Save changes made to person and redirect to person page
				PATH: movies.views.view_person urlname; METHOD: post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				person.Name = request.POST.get('name')
				try:
					person.full_clean()
					person.save()
					property_logger.info(person.UrlName + ' Update Success by ' + logged_in_profile_username)
					set_msg(request, 'Person Updated!', person.Name + ' has successfully been updated.', 3)
					return redirect('movies.views.view_person', urlname=person.UrlName)
				except ValidationError as e:
					property_logger.info(person.UrlName + ' Update Failure by ' + logged_in_profile_username)
					error_msg = e.message_dict
					for key in error_msg:
						error_msg[key] = str(error_msg[key][0])
					return render_to_response('property/edit_person.html', {'header' : generate_header_dict(request, 'Update'), 'person' : person, 'directed_movies' : directed_movies, 'written_movies' : written_movies, 'acted_movies' : acted_movies, 'error_msg' : error_msg}, RequestContext(request))
			else:
				'''*****************************************************************************
				Display edit page
				PATH: movies.views.view_person urlname; METHOD: not post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				return render_to_response('property/edit_person.html', {'header' : generate_header_dict(request, 'Update'), 'person' : person, 'directed_movies' : directed_movies, 'written_movies' : written_movies, 'acted_movies' : acted_movies}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('delete'):
			'''*****************************************************************************
			Delete person and redirect to home
			PATH: movies.views.view_person urlname; METHOD: none; PARAMS: get - delete; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			# Delete all movie associations with person
			for prop in directed_properties:
				prop.delete()
				associate_logger.info(prop.MovieId.UrlTitle + ' Disassociated ' + person.UrlName + ' Success by ' + logged_in_profile_username)
			for prop in written_properties:
				prop.delete()
				associate_logger.info(prop.MovieId.UrlTitle + ' Disassociated ' + person.UrlName + ' Success by ' + logged_in_profile_username)
			for prop in acted_properties:
				prop.delete()
				associate_logger.info(prop.MovieId.UrlTitle + ' Disassociated ' + person.UrlName + ' Success by ' + logged_in_profile_username)
			# Delete person
			person.delete()
			property_logger.info(person.UrlName + ' Delete Success by ' + logged_in_profile_username)
			set_msg(request, 'Person Deleted!', person.Name + ' has successfully been deleted.', 5)
			return redirect('movies.views.home')
		elif logged_in_profile_admin and request.GET.get('add') and request.method == 'POST':
			'''*****************************************************************************
			Create movie association with person and redirect to edit page
			PATH: movies.views.view_person urlname; METHOD: post; PARAMS: get - add; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			try:
				value = request.POST.get('add')
				title = value[:len(value) - 7] if value and len(value) > 7 else None
				year = int(value[len(value) - 5:len(value)-1]) if value and len(value) > 7 and value[len(value) - 5:len(value)-1].isdigit() else None
				type = int(request.GET.get('t')) if request.GET.get('t') and request.GET.get('t').isdigit() else -1
				movie = Movies.objects.get(Title=title, Year=year)
				create_movie_property(movie, person.id, person.UrlName, type, logged_in_profile_username)
				directed_properties = MovieProperties.objects.select_related().filter(Type=0, PropertyId=person.id)
				written_properties = MovieProperties.objects.select_related().filter(Type=1, PropertyId=person.id)
				acted_properties = MovieProperties.objects.select_related().filter(Type=2, PropertyId=person.id)
				directed_movies, written_movies, acted_movies = [], [], []
				for prop in directed_properties:
					directed_movies.append(prop.MovieId)
				for prop in written_properties:
					written_movies.append(prop.MovieId)
				for prop in acted_properties:
					acted_movies.append(prop.MovieId)
				set_msg(request, 'Movie Added!', movie.Title + ' has successfully been added to ' + person.Name + '\'s career.', 3)
				return render_to_response('property/edit_person.html', {'header' : generate_header_dict(request, 'Update'), 'person' : person, 'directed_movies' : directed_movies, 'written_movies' : written_movies, 'acted_movies' : acted_movies}, RequestContext(request))
			except ObjectDoesNotExist:
				property_logger.info(value + ' Added to ' + person.UrlName + ' Failure by ' + logged_in_profile_username)
				return render_to_response('property/edit_person.html', {'header' : generate_header_dict(request, 'Update'), 'person' : person, 'directed_movies' : directed_movies, 'written_movies' : written_movies, 'acted_movies' : acted_movies, 'error_msg' : {'MovieTitle' : 'Movie does not exist.'}}, RequestContext(request))
			except Exception:
				property_logger.info(value + ' Added to ' + person.UrlName + ' Failure by ' + logged_in_profile_username)
				return render_to_response('property/edit_person.html', {'header' : generate_header_dict(request, 'Update'), 'person' : person, 'directed_movies' : directed_movies, 'written_movies' : written_movies, 'acted_movies' : acted_movies, 'error_msg' : {'Movie' : 'Movie not found.'}}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('remove'):
			'''*****************************************************************************
			Remove property association with movie and redirect to home or edit page appropriately
			PATH: movies.views.view_person urlname; METHOD: none; PARAMS: get - remove; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			re = True if request.GET.get('redirect') else False
			id = request.GET.get('i')
			type = int(request.GET.get('t')) if request.GET.get('t') and request.GET.get('t').isdigit() else -1
			movie = Movies.objects.get(UrlTitle=id)
			prop = MovieProperties.objects.get(MovieId=movie, PropertyId=person.id, Type=type)
			prop.delete()
			associate_logger.info(movie.UrlTitle + ' Disassociated ' + person.UrlName + ' Success by ' + logged_in_profile_username)
			if person_is_relevant(person):
				if re:
					set_msg(request, 'Person Removed!', person.Name + ' has successfully been removed from ' + movie.Title + '.', 4)
					response = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					response['Location'] += '?edit=1'
					return response
				else:
					set_msg(request, 'Movie Removed!', movie.Title + ' has successfully been removed from ' + person.Name + ' \'s career.', 4)
					response = redirect('movies.views.view_person', urlname=person.UrlName)
					response['Location'] += '?edit=1'
					return response
			else:
				if re:
					person.delete()
					property_logger.info(person.Name + ' Delete Success by' + logged_in_profile_username)
					set_msg(request, 'Person Deleted!', person.Name + ' has successfully been deleted due to the removal of them from ' + movie.Title + '.', 5)
					response = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					response['Location'] += '?edit=1'
					return response
				else:
					person.delete()
					property_logger.info(person.Name + ' Delete Success by' + logged_in_profile_username)
					set_msg(request, 'Person Deleted!', person.Name + ' has successfully been deleted due to the removal of ' + movie.Title + ' from their career.', 5)
					return redirect('movies.views.home')
		else:
			'''*****************************************************************************
			Display person page
			PATH: movies.views.view_person urltitle; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			return render_to_response('property/view_person.html', {'header' : generate_header_dict(request, person.Name), 'person' : person, 'directed_movies' : directed_movies_tuples, 'written_movies' : written_movies_tuples, 'acted_movies' : acted_movies_tuples}, RequestContext(request))
	except ObjectDoesNotExist:
		raise Http404
	except Exception:
		property_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display genre list
def view_genres(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		'''*****************************************************************************
		Display genre list page
		PATH: movies.views.view_genres; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		genres = None
		genre_list = Genres.objects.all().order_by('Description')
		length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 25
		length = length if length <= 100 else 100
		paginator = Paginator(genre_list, length)
		page = request.GET.get('page')
		try:
			genres = paginator.page(page)
		except PageNotAnInteger:
			genres = paginator.page(1)
		except EmptyPage:
			genres = paginator.page(paginator.num_pages)
		return render_to_response('property/view_genre_list.html', {'header' : generate_header_dict(request, 'Genre List'), 'genres' : genres}, RequestContext(request))
	except Exception:
		property_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Genre tools including view, delete, edit, suggestion, and movie association tools (add, remove)
def view_genre(request, description):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		genre = Genres.objects.get(Description=description)
		properties = MovieProperties.objects.select_related().filter(Type=3, PropertyId=genre.id).order_by('-MovieId__Year', 'MovieId__Title')
		length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 25
		length = length if length <= 100 else 100
		paginator = Paginator(properties, length)
		page = request.GET.get('page')
		try:
			genre_movies = paginator.page(page)
		except PageNotAnInteger:
			genre_movies = paginator.page(1)
		except EmptyPage:
			genre_movies = paginator.page(paginator.num_pages)
		movies, movies_tuples = [], []
		for prop in genre_movies:
			movie = prop.MovieId
			movies.append(movie)
			try:
				association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = movie)
				movies_tuples.append((movie, True))
			except Exception:
				movies_tuples.append((movie, False))
		if request.GET.get('suggestion'):
			if request.method == 'POST':
				'''*****************************************************************************
				Send suggestion/comment/correction email and redirect to genre page
				PATH: movies.views.view_genre description; METHOD: post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				profile = Profiles.objects.get(id=logged_in_profile_id)
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id) + ' GenreId: ' + str(genre.id)
				email_message = request.POST.get('message') if request.POST.get('message') else None
				set_msg(request, 'Thank you for your feedback!', 'We have recieved your suggestion/comment/correction and will react to it appropriately.', 3)
				if email_message:
					# send email
					pass
				else:
					pass
				return redirect('movies.views.view_genre', description=genre.Description)
			else:
				'''*****************************************************************************
				Display suggestion/comment/correction page
				PATH: movies.views.view_genre description; METHOD: not post; PARAMS: get - suggestion; MISC: none;
				*****************************************************************************'''
				return render_to_response('suggestion_form.html', {'header' : generate_header_dict(request, 'Suggestion/Comment/Correction'), 'genre' : genre}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('edit'):
			if request.method == 'POST':
				'''*****************************************************************************
				Save changes made to genre and redirect to genre page
				PATH: movies.views.view_genre description; METHOD: post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				genre.Description = request.POST.get('description')
				try:
					genre.full_clean()
					genre.save()
					property_logger.info(genre.Description + ' Update Success by ' + logged_in_profile_username)
					set_msg(request, 'Genre Updated!', genre.Description + ' has successfully been updated.', 3)
					return redirect('movies.views.view_genre', description=genre.Description)
				except ValidationError as e:
					property_logger.info(genre.Description + ' Update Failure by ' + logged_in_profile_username)
					error_msg = e.message_dict
					for key in error_msg:
						error_msg[key] = str(error_msg[key][0])
					return render_to_response('property/edit_genre.html', {'header' : generate_header_dict(request, 'Update'), 'genre' : genre, 'movies' : movies, 'error_msg' : error_msg}, RequestContext(request))
			else:
				'''*****************************************************************************
				Display edit page
				PATH: movies.views.view_genre description; METHOD: not post; PARAMS: get - edit; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				return render_to_response('property/edit_genre.html', {'header' : generate_header_dict(request, 'Update'), 'genre' : genre, 'movies' : movies}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('delete'):
			'''*****************************************************************************
			Delete genre and redirect to home
			PATH: movies.views.view_genre description; METHOD: none; PARAMS: get - delete; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			for prop in properties:
				prop.delete()
				associate_logger.info(prop.MovieId.UrlTitle + ' Disassociated ' + genre.Description + ' Success by ' + logged_in_profile_username)
			genre.delete()
			property_logger.info(genre.Description + ' Delete Success by' + logged_in_profile_username)
			set_msg(request, 'Genre Deleted!', genre.Description + ' has successfully been deleted.', 5)
			return redirect('movies.views.home')
		elif logged_in_profile_admin and request.GET.get('add') and request.method == 'POST':
			try:
				'''*****************************************************************************
				Create movie association with genre and redirect to edit page
				PATH: movies.views.view_genre description; METHOD: post; PARAMS: get - add; MISC: logged_in_profile.IsAdmin;
				*****************************************************************************'''
				value = request.POST.get('add')
				title = value[:len(value) - 7] if value and len(value) > 7 else None
				year = int(value[len(value) - 5:len(value)-1]) if value and len(value) > 7 and value[len(value) - 5:len(value)-1].isdigit() else None
				movie = Movies.objects.get(Title=title, Year=year)
				create_movie_property(movie, genre.id, genre.Description, 3, logged_in_profile_username)
				properties = MovieProperties.objects.select_related().filter(Type=3, PropertyId=genre.id)
				movies = []
				for prop in properties:
					movies.append(prop.MovieId)
				set_msg(request, 'Movie Added!', movie.Title + ' has successfully been added to' + genre.Description + ' movies.', 4)
				return render_to_response('property/edit_genre.html', {'header' : generate_header_dict(request, 'Update'), 'genre' : genre, 'movies' : movies}, RequestContext(request))
			except ObjectDoesNotExist:
				property_logger.info(value + ' Added to ' + genre.Description + ' Failure by ' + logged_in_profile_username)
				return render_to_response('property/edit_genre.html', {'header' : generate_header_dict(request, 'Update'), 'genre' : genre, 'movies' : movies, 'error_msg' : {'MovieTitle' : 'Movie does not exist.'}}, RequestContext(request))
			except Exception:
				property_logger.info(value + ' Added to ' + genre.Description + ' Failure by ' + logged_in_profile_username)
				return render_to_response('property/edit_genre.html', {'header' : generate_header_dict(request, 'Update'), 'genre' : genre, 'movies' : movies, 'error_msg' : {'MovieTitle' : 'Year must be between 1901 and 2155 (inclusive).'}}, RequestContext(request))
		elif logged_in_profile_admin and request.GET.get('remove'):
			'''*****************************************************************************
			Remove property association with movie and redirect to home or edit page appropriately
			PATH: movies.views.view_genre descsription; METHOD: none; PARAMS: get - remove; MISC: logged_in_profile.IsAdmin;
			*****************************************************************************'''
			re = True if request.GET.get('redirect') else False
			id = request.GET.get('i')
			movie = Movies.objects.get(UrlTitle=id)
			prop = MovieProperties.objects.get(MovieId=movie, PropertyId=genre.id, Type=3)
			prop.delete()
			associate_logger.info(movie.UrlTitle + ' Disassociated ' + genre.Description + ' Success by ' + logged_in_profile_username)
			if genre_is_relevant(genre):
				if re:
					set_msg(request, 'Genre Removed!', genre.Description + ' has successfully been removed from ' + movie.Title + '.', 4)
					response = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					response['Location'] += '?edit=1'
					return response
				else:
					set_msg(request, 'Movie Removed!', movie.Title + ' has successfully been removed from' + genre.Description + 'movies.', 4)
					response = redirect('movies.views.view_genre', description=genre.Description)
					response['Location'] += '?edit=1'
					return resposne
			else:
				if re:
					genre.delete()
					property_logger.info(genre.Description + ' Delete Success by' + logged_in_profile_username)
					set_msg(request, 'Genre Deleted!', genre.Description + ' has successfully been deleted due to the removal of it from ' + movie.Title + '.', 5)
					response = redirect('movies.views.view_movie', urltitle=movie.UrlTitle)
					response['Location'] += '?edit=1'
					return response
				else:
					genre.delete()
					property_logger.info(genre.Description + ' Delete Success by' + logged_in_profile_username)
					set_msg(request, 'Genre Deleted!', genre.Description + ' has successfully been deleted due to the removal of ' + movie.Title + ' from this genre.', 5)
					return redirect('movies.views.home')
		else:
			return render_to_response('property/view_genre.html', {'header' : generate_header_dict(request, genre.Description), 'genre' : genre, 'movies' : movies_tuples, 'page' : genre_movies}, RequestContext(request))
	except ObjectDoesNotExist:
		raise Http404
	except Exception:
		property_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display search results
def search(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		if request.GET.get('t'):
			'''*****************************************************************************
			Search for movie and display movie page or search results page appropriately
			PATH: movies.views.search; METHOD: none; PARAMS: get - t; MISC: none;
			*****************************************************************************'''
			term = request.GET.get('t')
			try:
				year = int(term[len(term) - 5:len(term)-1])
				title = term[:len(term) - 7]
				movie = None
				movie = Movies.objects.get(Title=title, Year=year)
				return redirect('movies.views.view_movie', urltitle = movie.UrlTitle)
			except Exception:
				pass
			length = int(request.GET.get('length')) if request.GET.get('length') and request.GET.get('length').isdigit() else 2
			length = length if length <= 20 else 20
			res_dict = movies_from_term(term, length)
			if res_dict.get('success'):
				new_movies = res_dict.get('movies')
				old_movies = []
				links = []
				i = 0
				while i < len(new_movies):
					movie = new_movies[i]
					found = False
					try:
						if movie.ImdbId:
							imdb_movie = Movies.objects.get(ImdbId=movie.ImdbId)
							found = True
							if (imdb_movie, False) not in old_movies and (imdb_movie, True) not in old_movies:
								try:
									association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = imdb_movie)
									old_movies.append((imdb_movie, True))
								except Exception:
									old_movies.append((imdb_movie, False))
					except ObjectDoesNotExist:
						pass
					try:
						if movie.NetflixId:
							netflix_movie = Movies.objects.get(NetflixId=movie.NetflixId)
							found = True
							if (netflix_movie, False) not in old_movies and (netflix_movie, True) not in old_movies:
								try:
									association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = netflix_movie)
									old_movies.append((netflix_movie, True))
								except Exception:
									old_movies.append((netflix_movie, False))
					except ObjectDoesNotExist:
						pass
					try:
						if movie.RottenTomatoesId:
							rt_movie = Movies.objects.get(RottenTomatoesId=movie.RottenTomatoesId)
							found = True
							if (rt_movie, False) not in old_movies and (rt_movie, True) not in old_movies:
								try:
									association = ProfileMovies.objects.get(ProfileId = logged_in_profile_id, MovieId = rt_movie)
									old_movies.append((rt_movie, True))
								except Exception:
									old_movies.append((rt_movie, False))
					except ObjectDoesNotExist:
						pass
					if found:
						del new_movies[i]
					else:
						i += 1
				i = 0
				while i < len(new_movies):
					if new_movies[i].ImdbId and new_movies[i].RottenTomatoesId and not new_movies[i].NetflixId:
						res = netflix_movie_from_title(new_movies[i], True)
						if res.get('id'):
							new_movies[i].NetflixId = res.get('id')
					elif new_movies[i].ImdbId and new_movies[i].NetflixId and not new_movies[i].RottenTomatoesId:
						res = rottentomatoes_movie_from_title(new_movies[i], True)
						if res.get('id'):
							new_movies[i].RottenTomatoesId = res.get('id')
					if new_movies[i].ImdbId and new_movies[i].RottenTomatoesId and new_movies[i].NetflixId:
						new_movies[i].UrlTitle = imdb_link_for_movie(new_movies[i])
						i += 1
					else:
						del new_movies[i]
				return render_to_response('movie/search.html', {'header' : generate_header_dict(request, 'Search Results'), 'success' : True, 'term' : urllib.unquote(term), 'length' : length, 'new_movies' : new_movies, 'old_movies' : old_movies}, RequestContext(request))
			else:
				site_logger.debug('Search Errors: ' + str(res_dict.get('error_list')))
				return render_to_response('movie/search.html', {'header' : generate_header_dict(request, 'Search Results'), 'success' : False, 'term' : urllib.unquote(term), 'results' : {'Error' : 'No results found.'} }, RequestContext(request))
		else:
			'''*****************************************************************************
			Empty search page
			PATH: movies.views.search; METHOD: none; PARAMS: none; MISC: none;
			*****************************************************************************'''
			return render_to_response('movie/search.html', {'header' : generate_header_dict(request, 'Search Results'), 'success' : False, 'results' : {'Error' : 'No results, did not search for anything.'}}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Show users how to get started using the website
def discovery(request):
	try:
		logged_in_profile_id = request.session.get('auth_profile_id')
		logged_in_profile_username = request.session.get('auth_profile_username')
		logged_in_profile_admin = request.session.get('admin')
		if not logged_in_profile_id:
			set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
			return redirect('movies.views.login')
		'''*****************************************************************************
		Display discovery page
		PATH: movies.views.discovery; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		return render_to_response('movie/discovery.html', {'header' : generate_header_dict(request, 'Find Movies')}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display about page and take general suggestions/comments/corrections
def about(request):
	try:
		if request.GET.get('suggestion'):
			logged_in_profile_id = request.session.get('auth_profile_id')
			logged_in_profile_username = request.session.get('auth_profile_username')
			logged_in_profile_admin = request.session.get('admin')
			if not logged_in_profile_id:
				set_msg(request, 'Action Failed!', 'You must be logged in to perform that action', 4)
				return redirect('movies.views.login')
			if request.method == 'POST':
				'''*****************************************************************************
				Send suggestion/comment/correction email and redirect to home page
				PATH: movies.views.about; METHOD: post; PARAMS: none; MISC: none;
				*****************************************************************************'''
				profile = Profiles.objects.get(id=logged_in_profile_id)
				email_from = profile.Email if profile.Email else ''
				email_subject = 'Profile: ' + str(profile.Username) + ' Id: ' + str(profile.id) + ' 404'
				email_message = request.POST.get('message') if request.POST.get('message') else None
				set_msg(request, 'Thank you for your feedback!', 'We have recieved your suggestion/comment/correction and will react to it appropriately.', 3)
				if email_message:
					# send email
					pass
				else:
					pass
				return redirect('movies.views.home')
			else:
				'''*****************************************************************************
				Display about page
				PATH: movies.views.about; METHOD: none; PARAMS: none; MISC: none;
				*****************************************************************************'''
				return render_to_response('suggestion_form.html', {'header' : generate_header_dict(request, 'Suggestion/Comment/Correction')}, RequestContext(request))
		return render_to_response('about.html', {'header': generate_header_dict(request, 'About')}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))

# Display disclaimers page
def disclaimers(request):
	try:
		'''*****************************************************************************
		Display disclaimers page
		PATH: movies.views.disclaimers; METHOD: none; PARAMS: none; MISC: none;
		*****************************************************************************'''
		return render_to_response('disclaimers.html', {'header': generate_header_dict(request, 'Disclaimers')}, RequestContext(request))
	except Exception:
		site_logger.error('Unexpected error: ' + str(sys.exc_info()[0]))
		return render_to_response('error.html', {'header' : generate_header_dict(request, 'Error')}, RequestContext(request))
	
